/*
 * linux-5.4/drivers/media/platform/sunxi-vin/vin-tdm/vin_tdm.c
 *
 * Copyright (c) 2007-2019 Allwinnertech Co., Ltd.
 *
 * Authors:  Zheng Zequn <zequnzheng@allwinnertech.com>
 *
 * This software is licensed under the terms of the GNU General Public
 * License version 2, as published by the Free Software Foundation, and
 * may be copied, distributed, and modified under those terms.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 */

#include <linux/platform_device.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/delay.h>
#include <media/v4l2-device.h>
#include <media/v4l2-mediabus.h>
#include <media/v4l2-subdev.h>
#include "vin_tdm.h"
#include "../platform/platform_cfg.h"
#include "../vin-video/vin_video.h"

#define TDM_MODULE_NAME "vin_tdm"

struct tdm_dev *glb_tdm[VIN_MAX_TDM];

static struct tdm_format sunxi_tdm_formats[] = {
	{
		.code = MEDIA_BUS_FMT_SBGGR8_1X8,
		.input_type = INPUTTPYE_8BIT,
		.input_bit_width = RAW_8BIT,
	}, {
		.code = MEDIA_BUS_FMT_SGBRG8_1X8,
		.input_type = INPUTTPYE_8BIT,
		.input_bit_width = RAW_8BIT,
	}, {
		.code = MEDIA_BUS_FMT_SGRBG8_1X8,
		.input_type = INPUTTPYE_8BIT,
		.input_bit_width = RAW_8BIT,
	}, {
		.code = MEDIA_BUS_FMT_SRGGB8_1X8,
		.input_type = INPUTTPYE_8BIT,
		.input_bit_width = RAW_8BIT,
	}, {
		.code = MEDIA_BUS_FMT_SBGGR10_1X10,
		.input_type = INPUTTPYE_10BIT,
		.input_bit_width = RAW_10BIT,
	}, {
		.code = MEDIA_BUS_FMT_SGBRG10_1X10,
		.input_type = INPUTTPYE_10BIT,
		.input_bit_width = RAW_10BIT,
	}, {
		.code = MEDIA_BUS_FMT_SGRBG10_1X10,
		.input_type = INPUTTPYE_10BIT,
		.input_bit_width = RAW_10BIT,
	}, {
		.code = MEDIA_BUS_FMT_SRGGB10_1X10,
		.input_type = INPUTTPYE_10BIT,
		.input_bit_width = RAW_10BIT,
	}, {
		.code = MEDIA_BUS_FMT_SBGGR12_1X12,
		.input_type = INPUTTPYE_12BIT,
		.input_bit_width = RAW_12BIT,
	}, {
		.code = MEDIA_BUS_FMT_SGBRG12_1X12,
		.input_type = INPUTTPYE_12BIT,
		.input_bit_width = RAW_12BIT,
	}, {
		.code = MEDIA_BUS_FMT_SGRBG12_1X12,
		.input_type = INPUTTPYE_12BIT,
		.input_bit_width = RAW_12BIT,
	}, {
		.code = MEDIA_BUS_FMT_SRGGB12_1X12,
		.input_type = INPUTTPYE_12BIT,
		.input_bit_width = RAW_12BIT,
	}
};

static void tdm_set_tx_blank(struct tdm_dev *tdm)
{
	csic_tdm_omode(tdm->id, 1);
	csic_tdm_set_hblank(tdm->id, TDM_TX_HBLANK);
	csic_tdm_set_bblank_fe(tdm->id, TDM_TX_VBLANK/2);
	csic_tdm_set_bblank_be(tdm->id, TDM_TX_VBLANK/2);
}

static void tdm_rx_bufs_free(struct tdm_rx_dev *tdm_rx)
{
	struct tdm_dev *tdm = container_of(tdm_rx, struct tdm_dev, tdm_rx[tdm_rx->id]);
	int i;

	if (tdm->tdm_rx_buf_en[tdm_rx->id] == 0)
		return;

	for (i = 0; i < tdm_rx->buf_cnt; i++) {
		struct tdm_buffer *buf = &tdm_rx->buf[i];
		struct vin_mm *mm = &tdm_rx->ion_man[i];

		mm->size = tdm_rx->buf_size;
		if (!buf->virt_addr)
			continue;

		mm->vir_addr = buf->virt_addr;
		mm->dma_addr = buf->dma_addr;
		os_mem_free(&tdm->pdev->dev, mm);

		buf->dma_addr = NULL;
		buf->virt_addr = NULL;
	}

	vin_log(VIN_LOG_TDM, "%s: all buffers were freed.\n", tdm_rx->subdev.name);

	tdm->tdm_rx_buf_en[tdm_rx->id] = 0;
	tdm_rx->buf_size = 0;
}

static int tdm_rx_bufs_alloc(struct tdm_rx_dev *tdm_rx, u32 size, u32 count)
{
	struct tdm_dev *tdm = container_of(tdm_rx, struct tdm_dev, tdm_rx[tdm_rx->id]);
	int i;

	if (tdm->tdm_rx_buf_en[tdm_rx->id] == 1)
		return 0;

	tdm_rx->buf_size = size;
	tdm_rx->buf_cnt = count;

	for (i = 0; i < tdm_rx->buf_cnt; i++) {
		struct tdm_buffer *buf = &tdm_rx->buf[i];
		struct vin_mm *mm = &tdm_rx->ion_man[i];

		mm->size = size;
		if (!os_mem_alloc(&tdm->pdev->dev, mm)) {
			buf->virt_addr = mm->vir_addr;
			buf->dma_addr = mm->dma_addr;
		}
		if (!buf->virt_addr || !buf->dma_addr) {
			vin_err("%s: Can't acquire memory for DMA buffer %d\n",	tdm_rx->subdev.name, i);
			tdm_rx_bufs_free(tdm_rx);
			return -ENOMEM;
		}
	}

	vin_log(VIN_LOG_TDM, "%s: allocing buffers successfully, buf_cnt and size is %d and %d.\n",
				tdm_rx->subdev.name, tdm_rx->buf_cnt, tdm_rx->buf_size);

	tdm->tdm_rx_buf_en[tdm_rx->id] = 1;
	return 0;
}

static int tdm_set_rx_cfg(struct tdm_rx_dev *tdm_rx)
{
	struct tdm_dev *tdm = container_of(tdm_rx, struct tdm_dev, tdm_rx[tdm_rx->id]);
	u32 size;
	int ret, i;

	csic_tdm_rx_set_buf_num(tdm->id, tdm_rx->id, TDM_BUFS_NUM - 1);
	csic_tdm_rx_ch0_en(tdm->id, tdm_rx->id, 1);
	csic_tdm_rx_set_min_ddr_size(tdm->id, tdm_rx->id, DDRSIZE_256b);
	csic_tdm_rx_input_bit(tdm->id, tdm_rx->id, tdm_rx->tdm_fmt->input_type);
	csic_tdm_rx_input_size(tdm->id, tdm_rx->id, tdm_rx->format.width, tdm_rx->format.height);

	tdm_rx->width = tdm_rx->format.width;
	tdm_rx->heigtht = tdm_rx->format.height;
	size = (roundup(tdm_rx->width, 512)) * tdm_rx->heigtht * tdm_rx->tdm_fmt->input_bit_width / 8;
	ret = tdm_rx_bufs_alloc(tdm_rx, size, TDM_BUFS_NUM);
	if (ret)
		return ret;
	for (i = 0; i < TDM_BUFS_NUM; i++)
		csic_tdm_rx_set_address(tdm->id, tdm_rx->id, (unsigned long)tdm_rx->buf[i].dma_addr);

	return 0;

}

static int sunxi_tdm_subdev_s_stream(struct v4l2_subdev *sd, int enable)
{
	struct tdm_rx_dev *tdm_rx = v4l2_get_subdevdata(sd);
	struct tdm_dev *tdm = container_of(tdm_rx, struct tdm_dev, tdm_rx[tdm_rx->id]);
	struct v4l2_mbus_framefmt *mf = &tdm_rx->format;
	struct mbus_framefmt_res *res = (void *)mf->reserved;
	int i;

	switch (res->res_pix_fmt) {
	case V4L2_PIX_FMT_SBGGR8:
	case V4L2_PIX_FMT_SGBRG8:
	case V4L2_PIX_FMT_SGRBG8:
	case V4L2_PIX_FMT_SRGGB8:
	case V4L2_PIX_FMT_SBGGR10:
	case V4L2_PIX_FMT_SGBRG10:
	case V4L2_PIX_FMT_SGRBG10:
	case V4L2_PIX_FMT_SRGGB10:
	case V4L2_PIX_FMT_SBGGR12:
	case V4L2_PIX_FMT_SGBRG12:
	case V4L2_PIX_FMT_SGRBG12:
	case V4L2_PIX_FMT_SRGGB12:
		vin_log(VIN_LOG_FMT, "%s output fmt is raw, return directly\n", __func__);
		return 0;
	default:
		break;
	}

	if (enable) {
		tdm->stream_cnt++;
		tdm_rx->stream_cnt++;

		if (tdm->stream_cnt == 1)
			csic_tdm_top_enable(tdm->id);

		if (tdm_rx->stream_cnt == 1) {
			tdm_set_rx_cfg(tdm_rx);
			vin_log(VIN_LOG_TDM, "tdm_rx%d open first, setting rx configuration!\n", tdm_rx->id);
		} else {
			if (tdm_rx->width != tdm_rx->format.width || tdm_rx->heigtht != tdm_rx->format.height) {
				vin_err("The size of the %d time opening tdm_rx%d is different from the first time opened\n",
					tdm_rx->stream_cnt, tdm_rx->id);
				return -1;
			}
		}
		if (tdm->stream_cnt == 1) {
			tdm_set_tx_blank(tdm);
			csic_tdm_fifo_max_layer_en(tdm->id, 1);
			csic_tdm_int_enable(tdm->id, RX_FRM_LOST_INT_EN | RX_FRM_ERR_INT_EN |
					RX_BTYPE_ERR_INT_EN | RX_BUF_FULL_INT_EN | RX_COMP_ERR_INT_EN |
					RX_HB_SHORT_INT_EN | RX_FIFO_FULL_INT_EN);
			csic_tdm_enable(tdm->id);
			csic_tdm_tx_cap_enable(tdm->id);
			vin_log(VIN_LOG_TDM, "tdm%d open first, setting the interrupt and tx configuration!\n", tdm->id);
		}
		csic_tdm_rx_enable(tdm->id, tdm_rx->id);
		csic_tdm_rx_cap_enable(tdm->id, tdm_rx->id);
	} else {
		tdm->stream_cnt--;
		if (tdm->stream_cnt == 0) {
			csic_tdm_int_disable(tdm->id, TDM_INT_ALL);
			csic_tdm_rx_cap_disable(tdm->id, tdm_rx->id);
			csic_tdm_tx_cap_disable(tdm->id);
			csic_tdm_top_disable(tdm->id);

			for (i = 0; i < TDM_RX_NUM; i++) {
				if (tdm->tdm_rx_buf_en[i] == 1)
					tdm_rx_bufs_free(&tdm->tdm_rx[i]);
				tdm->tdm_rx[i].stream_cnt = 0;
			}
			vin_log(VIN_LOG_TDM, "tdm%d close, closing the interrupt and tx/rx configuration!\n", tdm->id);
		} else
			vin_warn("TDM is used, cannot stream off!\n");
	}

	vin_log(VIN_LOG_FMT, "tdm_rx%d %s, %d*%d==%d*%d?\n",
			tdm_rx->id, enable ? "stream on" : "stream off",
			tdm_rx->width, tdm_rx->heigtht,
			tdm_rx->format.width, tdm_rx->format.height);
	return 0;
}


static int sunxi_tdm_subdev_get_fmt(struct v4l2_subdev *sd,
				     struct v4l2_subdev_pad_config *cfg,
				     struct v4l2_subdev_format *fmt)
{
	struct tdm_rx_dev *tdm_rx = v4l2_get_subdevdata(sd);
	struct v4l2_mbus_framefmt *mf;

	mf = &tdm_rx->format;
	if (!mf)
		return -EINVAL;

	mutex_lock(&tdm_rx->subdev_lock);
	fmt->format = *mf;
	mutex_unlock(&tdm_rx->subdev_lock);
	return 0;
}

static struct tdm_format *__tdm_try_format(struct v4l2_mbus_framefmt *mf)
{
	struct tdm_format *tdm_fmt = NULL;
	int i;

	for (i = 0; i < ARRAY_SIZE(sunxi_tdm_formats); i++)
		if (mf->code == sunxi_tdm_formats[i].code)
			tdm_fmt = &sunxi_tdm_formats[i];

	if (tdm_fmt == NULL)
		tdm_fmt = &sunxi_tdm_formats[0];

	mf->code = tdm_fmt->code;

	return tdm_fmt;
}

static int sunxi_tdm_subdev_set_fmt(struct v4l2_subdev *sd,
				     struct v4l2_subdev_pad_config *cfg,
				     struct v4l2_subdev_format *fmt)
{
	struct tdm_rx_dev *tdm_rx = v4l2_get_subdevdata(sd);
	struct v4l2_mbus_framefmt *mf;
	struct tdm_format *tdm_fmt;

	vin_log(VIN_LOG_FMT, "%s %d*%d %x %d\n", __func__,
		fmt->format.width, fmt->format.height,
		fmt->format.code, fmt->format.field);

	mf = &tdm_rx->format;

	if (fmt->pad == MIPI_PAD_SOURCE) {
		if (mf) {
			mutex_lock(&tdm_rx->subdev_lock);
			fmt->format = *mf;
			mutex_unlock(&tdm_rx->subdev_lock);
		}
		return 0;
	}

	tdm_fmt = __tdm_try_format(&fmt->format);
	if (mf) {
		mutex_lock(&tdm_rx->subdev_lock);
		*mf = fmt->format;
		if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE)
			tdm_rx->tdm_fmt = tdm_fmt;
		mutex_unlock(&tdm_rx->subdev_lock);
	}

	return 0;
}


static const struct v4l2_subdev_video_ops sunxi_tdm_subdev_video_ops = {
	.s_stream = sunxi_tdm_subdev_s_stream,
};

static const struct v4l2_subdev_pad_ops sunxi_tdm_subdev_pad_ops = {
	.get_fmt = sunxi_tdm_subdev_get_fmt,
	.set_fmt = sunxi_tdm_subdev_set_fmt,
};

static struct v4l2_subdev_ops sunxi_tdm_subdev_ops = {
	.video = &sunxi_tdm_subdev_video_ops,
	.pad = &sunxi_tdm_subdev_pad_ops,
};

static int __tdm_init_subdev(struct tdm_rx_dev *tdm_rx)
{
	struct v4l2_subdev *sd = &tdm_rx->subdev;
	int ret;

	mutex_init(&tdm_rx->subdev_lock);
	v4l2_subdev_init(sd, &sunxi_tdm_subdev_ops);
	sd->grp_id = VIN_GRP_ID_TDM_RX;
	sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
	snprintf(sd->name, sizeof(sd->name), "sunxi_tdm_rx.%u", tdm_rx->id);
	v4l2_set_subdevdata(sd, tdm_rx);

	tdm_rx->tdm_pads[TDM_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
	tdm_rx->tdm_pads[TDM_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
	sd->entity.function = MEDIA_ENT_F_IO_V4L;

	ret = media_entity_pads_init(&sd->entity, TDM_PAD_NUM, tdm_rx->tdm_pads);
	if (ret < 0)
		return ret;

	return 0;
}

static void __sunxi_tdm_reset(struct tdm_dev *tdm)
{
	struct tdm_rx_dev *tdm_rx;
	struct vin_md *vind = dev_get_drvdata(tdm->tdm_rx[0].subdev.v4l2_dev->dev);
	struct vin_core *vinc = NULL;
	struct prs_cap_mode mode = {.mode = VCAP};
	bool flags = 1;
	int i, j, w;

	vin_print("%s:tdm%d reset!!!\n", __func__, tdm->id);
	/*****************stop*******************/
	for (i = 0; i < VIN_MAX_DEV; i++) {
		if (vind->vinc[i] == NULL)
			continue;
		if (!vin_streaming(&vind->vinc[i]->vid_cap))
			continue;

		for (j = 0; j < TDM_RX_NUM; j++) {
			if (!tdm->tdm_rx[j].stream_cnt)
				continue;
			if (vind->vinc[i]->tdm_rx_sel == tdm->tdm_rx[j].id) {
				vinc = vind->vinc[i];
				tdm_rx = &tdm->tdm_rx[j];

				vin_print("video%d reset, frame count is %d\n", vinc->id, vinc->vin_status.frame_cnt);

				if (flags) {
					csic_prs_capture_stop(vinc->csi_sel);

					csic_tdm_int_clear_status(tdm->id, TDM_INT_ALL);
					csic_tdm_rx_cap_disable(tdm->id, tdm_rx->id);
					csic_tdm_rx_disable(tdm->id, tdm_rx->id);
					csic_tdm_tx_cap_disable(tdm->id);
					csic_tdm_disable(tdm->id);
					csic_tdm_top_disable(tdm->id);

					bsp_isp_clr_irq_status(vinc->isp_sel, ISP_IRQ_EN_ALL);
					for (w = 0; w < VIN_MAX_ISP; w++)
						bsp_isp_enable(w, 0);
					bsp_isp_capture_stop(vinc->isp_sel);
				}
				vipp_disable(vinc->vipp_sel);
				vipp_top_clk_en(vinc->vipp_sel, 0);
				csic_dma_int_clear_status(vinc->vipp_sel, DMA_INT_ALL);
				csic_dma_top_disable(vinc->vipp_sel);

				if (flags) {
					csic_prs_disable(vinc->csi_sel);
					csic_isp_bridge_disable(0);
				}
			}
		}
	}

	/*****************start*******************/
	for (i = 0; i < VIN_MAX_DEV; i++) {
		if (vind->vinc[i] == NULL)
			continue;
		if (!vin_streaming(&vind->vinc[i]->vid_cap))
			continue;

		for (j = 0; j < TDM_RX_NUM; j++) {
			if (!tdm->tdm_rx[j].stream_cnt)
				continue;
			if (vind->vinc[i]->tdm_rx_sel == tdm->tdm_rx[j].id) {
				vinc = vind->vinc[i];
				tdm_rx = &tdm->tdm_rx[j];

				csic_dma_top_enable(vinc->vipp_sel);
				vipp_top_clk_en(vinc->vipp_sel, 1);
				vipp_enable(vinc->vipp_sel);
				vinc->vin_status.frame_cnt = 0;
				vinc->vin_status.lost_cnt = 0;

				if (flags) {
					for (w = 0; w < VIN_MAX_ISP; w++)
						bsp_isp_enable(w, 1);
					bsp_isp_capture_start(vinc->isp_sel);

					csic_isp_bridge_enable(0);

					csic_tdm_top_enable(tdm->id);
					csic_tdm_enable(tdm->id);
					csic_tdm_tx_cap_enable(tdm->id);
					csic_tdm_rx_enable(tdm->id, tdm_rx->id);
					csic_tdm_rx_cap_enable(tdm->id, tdm_rx->id);

					csic_prs_enable(vinc->csi_sel);
					csic_prs_capture_start(vinc->csi_sel, 1, &mode);
				}
			}
		}
	}
}

static irqreturn_t tdm_isr(int irq, void *priv)
{
	struct tdm_dev *tdm = (struct tdm_dev *)priv;
	struct tdm_int_status status;
	unsigned int hb_min = 0xffff, hb_max = 0;
	unsigned int width = 0, height = 0;
	unsigned long flags;

	if (tdm->stream_cnt == 0) {
		csic_tdm_int_clear_status(tdm->id, TDM_INT_ALL);
		return IRQ_HANDLED;
	}

	csic_tdm_int_get_status(tdm->id, &status);

	spin_lock_irqsave(&tdm->slock, flags);

	if (status.rx_frm_lost) {
		csic_tdm_int_clear_status(tdm->id, RX_FRM_LOST_INT_EN);
		if (csic_tdm_internal_get_status0(tdm->id, RX0_FRM_LOST_PD)) {
			vin_err("tdm%d rx0 frame lost!\n", tdm->id);
			csic_tdm_internal_clear_status0(tdm->id, RX0_FRM_LOST_PD);
		}
		if (csic_tdm_internal_get_status0(tdm->id, RX1_FRM_LOST_PD)) {
			vin_err("tdm%d rx1 frame lost!\n", tdm->id);
			csic_tdm_internal_clear_status0(tdm->id, RX1_FRM_LOST_PD);
		}
		__sunxi_tdm_reset(tdm);
	}

	if (status.rx_frm_err) {
		csic_tdm_int_clear_status(tdm->id, RX_FRM_ERR_INT_EN);
		if (csic_tdm_internal_get_status0(tdm->id, RX0_FRM_ERR_PD)) {
			csic_tdm_rx_get_size(tdm->id, 0, &width, &height);
			vin_err("tdm%d rx0 frame error! width is %d, height is %d\n", tdm->id, width, height);
			csic_tdm_internal_clear_status0(tdm->id, RX0_FRM_ERR_PD);
		}
		if (csic_tdm_internal_get_status0(tdm->id, RX1_FRM_ERR_PD)) {
			csic_tdm_rx_get_size(tdm->id, 1, &width, &height);
			vin_err("tdm%d rx1 frame error! width is %d, height is %d\n", tdm->id, width, height);
			csic_tdm_internal_clear_status0(tdm->id, RX1_FRM_ERR_PD);
		}
		__sunxi_tdm_reset(tdm);
	}

	if (status.rx_btype_err) {
		csic_tdm_int_clear_status(tdm->id, RX_BTYPE_ERR_INT_EN);
		if (csic_tdm_internal_get_status0(tdm->id, RX0_BTYPE_ERR_PD)) {
			vin_err("tdm%d rx0 btype error!\n", tdm->id);
			csic_tdm_internal_clear_status0(tdm->id, RX0_BTYPE_ERR_PD);
		}
		if (csic_tdm_internal_get_status0(tdm->id, RX1_BTYPE_ERR_PD)) {
			vin_err("tdm%d rx1 btype error!\n", tdm->id);
			csic_tdm_internal_clear_status0(tdm->id, RX1_BTYPE_ERR_PD);
		}
		__sunxi_tdm_reset(tdm);
	}

	if (status.rx_buf_full) {
		csic_tdm_int_clear_status(tdm->id, RX_BUF_FULL_INT_EN);
		if (csic_tdm_internal_get_status0(tdm->id, RX0_BUF_FULL_PD)) {
			vin_err("tdm%d rx0 buffer full!\n", tdm->id);
			csic_tdm_internal_clear_status0(tdm->id, RX0_BUF_FULL_PD);
		}
		if (csic_tdm_internal_get_status0(tdm->id, RX1_BUF_FULL_PD)) {
			vin_err("tdm%d rx1 buffer full!\n", tdm->id);
			csic_tdm_internal_clear_status0(tdm->id, RX1_BUF_FULL_PD);
		}
		__sunxi_tdm_reset(tdm);
	}

	if (status.rx_comp_err) {
		csic_tdm_int_clear_status(tdm->id, RX_COMP_ERR_INT_EN);
		if (csic_tdm_internal_get_status1(tdm->id, RX0_COMP_ERR_PD)) {
			vin_err("tdm%d rx0 compose error!\n", tdm->id);
			csic_tdm_internal_clear_status1(tdm->id, RX0_COMP_ERR_PD);
		}
		if (csic_tdm_internal_get_status1(tdm->id, RX1_COMP_ERR_PD)) {
			vin_err("tdm%d rx1 compose error!\n", tdm->id);
			csic_tdm_internal_clear_status1(tdm->id, RX1_COMP_ERR_PD);
		}
		__sunxi_tdm_reset(tdm);
	}

	if (status.rx_hb_short) {
		csic_tdm_int_clear_status(tdm->id, RX_HB_SHORT_INT_EN);
		if (csic_tdm_internal_get_status1(tdm->id, RX0_HB_SHORT_PD)) {
			csic_tdm_rx_get_hblank(tdm->id, 0, &hb_min, &hb_max);
			vin_err("tdm%d rx0 hblank short! min is %d, max is %d\n", tdm->id, hb_min, hb_max);
			csic_tdm_internal_clear_status1(tdm->id, RX0_HB_SHORT_PD);
		}
		if (csic_tdm_internal_get_status1(tdm->id, RX1_HB_SHORT_PD)) {
			csic_tdm_rx_get_hblank(tdm->id, 1, &hb_min, &hb_max);
			vin_err("tdm%d rx1 hblank short! min is %d, max is %d\n", tdm->id, hb_min, hb_max);
			csic_tdm_internal_clear_status1(tdm->id, RX1_HB_SHORT_PD);
		}
		__sunxi_tdm_reset(tdm);
	}

	if (status.rx_fifo_full) {
		csic_tdm_int_clear_status(tdm->id, RX_FIFO_FULL_INT_EN);
		if (csic_tdm_internal_get_status1(tdm->id, RX0_FIFO_FULL_PD)) {
			vin_err("tdm%d rx0 write DMA fifo overflow!\n", tdm->id);
			csic_tdm_internal_clear_status1(tdm->id, RX0_FIFO_FULL_PD);
		}
		if (csic_tdm_internal_get_status1(tdm->id, RX1_FIFO_FULL_PD)) {
			vin_err("tdm%d rx1 write DMA fifo overflow!\n", tdm->id);
			csic_tdm_internal_clear_status1(tdm->id, RX1_FIFO_FULL_PD);
		}
		__sunxi_tdm_reset(tdm);
	}

	spin_unlock_irqrestore(&tdm->slock, flags);

	return IRQ_HANDLED;
}


static int tdm_probe(struct platform_device *pdev)
{
	struct device_node *np = pdev->dev.of_node;
	struct tdm_dev *tdm = NULL;
	unsigned int i;
	int ret = 0;

	if (np == NULL) {
		vin_err("TDM failed to get of node\n");
		return -ENODEV;
	}

	tdm = kzalloc(sizeof(struct tdm_dev), GFP_KERNEL);
	if (!tdm) {
		ret = -ENOMEM;
		goto ekzalloc;
	}
	of_property_read_u32(np, "device_id", &pdev->id);
	if (pdev->id < 0) {
		vin_err("TDM failed to get device id\n");
		ret = -EINVAL;
		goto freedev;
	}

	tdm->id = pdev->id;
	tdm->pdev = pdev;
	tdm->stream_cnt = 0;

	tdm->base = of_iomap(np, 0);
	if (!tdm->base) {
		tdm->is_empty = 1;
	} else {
		tdm->is_empty = 0;
		/*get irq resource */
		tdm->irq = irq_of_parse_and_map(np, 0);
		if (tdm->irq <= 0) {
			vin_err("failed to get TDM IRQ resource\n");
			goto unmap;
		}
		ret = request_irq(tdm->irq, tdm_isr, IRQF_SHARED, tdm->pdev->name, tdm);
		if (ret) {
			vin_err("tdm%d request tdm failed\n", tdm->id);
			goto unmap;
		}
	}

	spin_lock_init(&tdm->slock);

	csic_tdm_set_base_addr(tdm->id, (unsigned long)tdm->base);

	for (i = 0; i < TDM_RX_NUM; i++) {
		tdm->tdm_rx[i].id = i;
		ret = __tdm_init_subdev(&tdm->tdm_rx[i]);
		if (ret < 0) {
			vin_err("tdm_rx%d init error!\n", i);
			goto unmap;
		}
		tdm->tdm_rx[i].stream_cnt = 0;
		tdm->tdm_rx_buf_en[i] = 0;
	}

	platform_set_drvdata(pdev, tdm);
	glb_tdm[tdm->id] = tdm;

	vin_log(VIN_LOG_TDM, "tdm%d probe end!\n", tdm->id);
	return 0;

unmap:
	iounmap(tdm->base);
freedev:
	kfree(tdm);
ekzalloc:
	vin_err("tdm probe err!\n");
	return ret;
}

static int tdm_remove(struct platform_device *pdev)
{
	struct tdm_dev *tdm = platform_get_drvdata(pdev);
	struct v4l2_subdev *sd;
	unsigned int i;

	platform_set_drvdata(pdev, NULL);

	if (!tdm->is_empty) {
		free_irq(tdm->irq, tdm);
		if (tdm->base)
			iounmap(tdm->base);
	}

	for (i = 0; i < TDM_RX_NUM; i++) {
		sd = &tdm->tdm_rx[i].subdev;
		v4l2_set_subdevdata(sd, NULL);
		media_entity_cleanup(&tdm->tdm_rx[i].subdev.entity);
	}

	kfree(tdm);
	return 0;
}

static const struct of_device_id sunxi_tdm_match[] = {
	{.compatible = "allwinner,sunxi-tdm",},
	{},
};

static struct platform_driver tdm_platform_driver = {
	.probe = tdm_probe,
	.remove = tdm_remove,
	.driver = {
		.name = TDM_MODULE_NAME,
		.owner = THIS_MODULE,
		.of_match_table = sunxi_tdm_match,
	}
};

struct v4l2_subdev *sunxi_tdm_get_subdev(int id, int tdm_rx_num)
{
	if (id < VIN_MAX_TDM && glb_tdm[id])
		return &glb_tdm[id]->tdm_rx[tdm_rx_num].subdev;
	else
		return NULL;
}

int sunxi_tdm_platform_register(void)
{
	return platform_driver_register(&tdm_platform_driver);
}

void sunxi_tdm_platform_unregister(void)
{
	platform_driver_unregister(&tdm_platform_driver);
	vin_log(VIN_LOG_TDM, "tdm_exit end\n");
}

